using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Globalization;
using System.Net;
using System.Threading;
using SimpleMessageQueue;
using slcl.Packets;
using Nwc.XmlRpc;
using Nii.JSON;

namespace slcl
{
    public class Grid
    {
         #region Enums

        /// <summary>
        /// things the Grid can do for us.
        /// </summary>
        public enum Command
        {
            Login,      // perform a login
            Logout,     // perform a logout
            Register,   // register for a apcket type
            Unregister, // unregister for a packet type
            Teleport,   // goto a specific location. teleport is special to the grid since we jump between sims.
            FetchAllSims, // request maplayers to find all sim Names and handles.
            LookupSim,   // lookup a sim.
            Shutdown    // shutdown things
        }

        public enum Event
        {
            Login,              // raised on the first sim connection
            Logout,             // raised on the last sim disconnection
            SimulatorConnected,    // raised on a sim completing it's connection
            SimulatorDisconnected, // raised on a sim completing it's disconnection
            CurrentSimChanged,     // new current sim.
            Packet,                // We got a packet from a sim Used as basic hook in for all sims.
            TeleportStatus,        // Teleportation status
            RegionAdd,             // Region added to list
            Shutdown               // raised on completion of shutdown
        }

        /// <summary>
        /// Explains why a simulator or the grid disconnected from us
        /// </summary>
        public enum DisconnectType
        {
            /// <summary>The client requested the logout or simulator disconnect</summary>
            ClientInitiated,
            /// <summary>The server notified us that it is disconnecting</summary>
            ServerInitiated,
            /// <summary>Either a socket was closed or Grid traffic timed out</summary>
            GridTimeout,
            /// <summary>The last active simulator shut down</summary>
            SimShutdown
        }


        /// <summary>
        /// 
        /// </summary>
        [Flags]
        public enum TeleportFlags : uint
        {
            /// <summary></summary>
            Default = 0,
            /// <summary></summary>
            SetHomeToTarget = 1 << 0,
            /// <summary></summary>
            SetLastToTarget = 1 << 1,
            /// <summary></summary>
            ViaLure = 1 << 2,
            /// <summary></summary>
            ViaLandmark = 1 << 3,
            /// <summary></summary>
            ViaLocation = 1 << 4,
            /// <summary></summary>
            ViaHome = 1 << 5,
            /// <summary></summary>
            ViaTelehub = 1 << 6,
            /// <summary></summary>
            ViaLogin = 1 << 7,
            /// <summary></summary>
            ViaGodlikeLure = 1 << 8,
            /// <summary></summary>
            Godlike = 1 << 9,
            /// <summary></summary>
            NineOneOne = 1 << 10,
            /// <summary></summary>
            DisableCancel = 1 << 11,
            /// <summary></summary>
            ViaRegionID = 1 << 12,
            /// <summary></summary>
            IsFlying = 1 << 13
        }

        /// <summary>
        /// 
        /// </summary>
        [Flags]
        public enum TeleportLureFlags
        {
            /// <summary></summary>
            NormalLure = 0,
            /// <summary></summary>
            GodlikeLure = 1,
            /// <summary></summary>
            GodlikePursuit = 2
        }

        /// <summary>
        /// Current teleport status
        /// </summary>
        public enum TeleportStatus
        {
            /// <summary>Unknown status</summary>
            None,
            /// <summary>Teleport initialized</summary>
            Start,
            /// <summary>Teleport in progress</summary>
            Progress,
            /// <summary>Teleport failed</summary>
            Failed,
            /// <summary>Teleport completed</summary>
            Finished,
            /// <summary>Teleport cancelled</summary>
            Cancelled
        }

        public enum MapLayerType : uint
        {
            Objects = 0,
            Terrain = 1
        }

        #endregion

        #region Vars

        /// <summary>
        /// who owns this grid link.
        /// </summary>
        public Client Client;

        /// <summary>
        /// The name of the grid.
        /// </summary>
        string Name;

        /// <summary>
        /// The login URI
        /// </summary>
        string LoginURI;


        // our main message queue.
        // If you want a specific packet type from the current sim forwarded on to you
        // then registermessage this queue. All packets are checked against this queue from all 
        // SimulatorLink objects.
        public MessageQueue MQueue;

        /// <summary>
        /// values that we login with
        /// </summary>
        public Dictionary<string, object> LoginValues;
        /// <summary>
        /// values that are returned to us after login.
        /// </summary>
        public Dictionary<string, object> LoginResponseValues;

        /// <summary>The permanent UUID for the logged in avatar</summary>
        public LLUUID AgentID = LLUUID.Zero;
        /// <summary>Temporary UUID assigned to this session, used for 
        /// verifying our identity in packets</summary>
        public LLUUID SessionID = LLUUID.Zero;
        /// <summary>Shared secret UUID that is never sent over the wire</summary>
        public LLUUID SecureSessionID = LLUUID.Zero;
        /// <summary>Uniquely identifier associated with our connections to
        /// simulators</summary>
        public uint CircuitCode;

        /// <summary>
        /// what sim we are "in"
        /// </summary>
        public Simulator Sim;

        /// <summary>
        /// Are we maintaining at least one Simulator link?
        /// </summary>
        public bool Connected;
       
        /// <summary>
        /// All of the simulators that we know about.
        /// </summary>
        public Dictionary<string, Simulator> SimulatorsByName;

        /// <summary>
        /// connected simulators.
        /// </summary>
        public Dictionary<IPEndPoint, Simulator> SimByConnection;

        /// <summary>
        /// A global throttle setting. It's used on every Simulator.SimLink object
        /// to set the bandwidth to that sim.
        /// </summary>
        public AgentThrottle Throttle;

        CAPSManager CapsManager;

        /// <summary>Current status message for teleporting</summary>
        public string TeleportMessage { get { return teleportMessage; } }
        private TeleportStatus TeleportStat = TeleportStatus.None;
        private ManualResetEvent TeleportEvent = new ManualResetEvent(false);
        internal string teleportMessage = String.Empty;


        System.Timers.Timer LogoutTimer;
        Dictionary<string, CommandMessage> SimLookupRequests = new Dictionary<string, CommandMessage>();

        private ManualResetEvent LoginInitialSimEvent = new ManualResetEvent(false);
        private ManualResetEvent LogoutReplyEvent = new ManualResetEvent(false);

        /// <summary>
        /// where we are teleporting to
        /// </summary>
        public SimLocation TeleportTarget;

        // holds a listing of who is interested in what packets.
        private SortedDictionary<PacketType, List<CommandMessage>> PacketRegistrations;

#endregion        /// <summary>

        public Grid(Client client, string name, string loginuri)
        {
            Client = client;
            Name = name;
            LoginURI = loginuri;
            LoginValues = DefaultLoginValues();
            PacketRegistrations = new SortedDictionary<PacketType, List<CommandMessage>>();
            SimulatorsByName = new Dictionary<string, Simulator>();
            SimByConnection = new Dictionary<IPEndPoint, Simulator>();
            Throttle = new AgentThrottle(this);
            CapsManager = new CAPSManager(this);
            // Grid gets to own the Grid manager since it's the hub of everything.
            if (Client.Settings.USE_INDEPENDANT_QUEUES)
            {
                MQueue = new MessageQueue(this, true);
            }
            else
            {
                MQueue = Client.MQueue;
            }
            MQueue.RegisterPacketCallback(Message.MessageType.Command
                                          , new MessageQueue.MessageCallback(HandleCommand));
            // we manage sims, so when asked WE setup new sim links.
                //            RegisterCallback(PacketType.EnableSimulator, new PacketCallback(EnableSimulatorHandler));
            Throttle.Land = 0.0f;
            Throttle.Cloud = 0.0f;
            Throttle.Wind = 0.0f;

            CommandMessage.AckCallback callback = new CommandMessage.AckCallback(TeleportHandler);
            MQueue.Send(new CommandMessage(Command.Register, PacketType.TeleportStart, callback), this);
            MQueue.Send(new CommandMessage(Command.Register, PacketType.TeleportProgress, callback), this);
            MQueue.Send(new CommandMessage(Command.Register, PacketType.TeleportFailed, callback), this);
            MQueue.Send(new CommandMessage(Command.Register, PacketType.TeleportFinish, callback), this);
            MQueue.Send(new CommandMessage(Command.Register, PacketType.TeleportCancel, callback), this);
            MQueue.Send(new CommandMessage(Command.Register, PacketType.TeleportLocal, callback), this);
            MQueue.Send(new CommandMessage(Command.Register, PacketType.MapBlockReply, new CommandMessage.AckCallback(MapBlockReplyHandler)), this);

            MQueue.Send(new RegisteredMessage("TeleportFinished", new CommandMessage.AckCallback(TeleportFinishedHandler)), CapsManager);
        }

        public void HandleCommand(Message msg)
        {
            if(!(msg is CommandMessage)) return;
            if(!(((CommandMessage)msg).Command is Command)) return;
            CommandMessage Msg = (CommandMessage)msg;
            switch ((Command)Msg.Command)
            {
                case Command.Login:
                    Login(Msg);
                    break;
                case Command.Logout:
                    Logout(Msg);
                    break;
                case Command.Teleport:
                    Teleport(Msg);
                    break;
                case Command.FetchAllSims:
                    FetchAllSims(MapLayerType.Objects);
                    MQueue.Ack(Msg, AckMessage.StatusType.Completed);
                    break;
                case Command.Register:
                    RegisterForPacket(Msg);
                    break;
                case Command.Unregister:
                    UnregisterForPacket(Msg);
                    break;
                case Command.LookupSim:
                    LookupSim(Msg);
                    break;
                case Command.Shutdown:
                    Shutdown();
                    MQueue.Send(new CommandMessage(CAPSManager.Command.Shutdown), CapsManager);
                    MQueue.Ack(Msg, AckMessage.StatusType.Completed);
                    MQueue.Shutdown();
                    break;
            }
        }

        // FIXME: we need to ack that it's a valid packet type I suppose?

        // for now mqueue will handle packet registration deliveries.

        /// <summary>
        /// ask to be sent certain packets.
        /// </summary>
        /// <param name="Msg"></param>
        void RegisterForPacket(Message Msg)
        {
            lock (PacketRegistrations)
            {
                PacketType type = (PacketType)Msg.Data;
                if (!PacketRegistrations.ContainsKey(type))
                    PacketRegistrations.Add(type, new List<CommandMessage>());
                PacketRegistrations[type].Add((CommandMessage)Msg);
            }
        }

        /// <summary>
        /// ask that packet delivery stop for a certain packet.
        /// </summary>
        /// <param name="Msg"></param>
        void UnregisterForPacket(Message Msg)
        {
            lock (PacketRegistrations)
            {            
                PacketType type = (PacketType)Msg.Data;
                if (!PacketRegistrations.ContainsKey(type))
                    return;
                foreach (CommandMessage regMsg in PacketRegistrations[type])
                {
                    if (regMsg.Sender == Msg.Sender
                        && regMsg.AckHandler == ((CommandMessage)Msg).AckHandler)
                    {
                        PacketRegistrations[type].Remove(regMsg);
                        return;
                    }
                }
            }
        }

        /// <summary>
        /// checks the PacketRegistrations list for interested parties 
        /// and sends an ack over to them.
        /// </summary>
        /// <param name="packet"></param>
        /// <param name="Sim"></param>
        public void DeliverInbound(Packet packet, Simulator Sim)
        {
            lock (PacketRegistrations)
            {
                if (PacketRegistrations.ContainsKey(packet.Type))
                {
                    bool sent = false;
                    PacketCarrier pcarrier = new PacketCarrier();
                    pcarrier.packet = packet;
                    pcarrier.sim = Sim;
               //     Log("DECODER: Pushing packet " + packet.Type.ToString(), LogLevel.Debug);
                    foreach (CommandMessage msg in PacketRegistrations[packet.Type])
                    {
                        MQueue.Ack(msg, AckMessage.StatusType.Progress, pcarrier);
                        sent = true;
                  //      Log("decoder: Packet Pushed.", LogLevel.Debug);
                    }
                    if(!sent)
                        Log("Packet type " + packet.Type.ToString() + " has no handler", LogLevel.Debug);
                }
            }

        }

        #region Utility Methods

        /// <summary>
        /// Build a start location URI for passing to the Login function
        /// </summary>
        /// <param name="sim">Name of the simulator to start in</param>
        /// <param name="x">X coordinate to start at</param>
        /// <param name="y">Y coordinate to start at</param>
        /// <param name="z">Z coordinate to start at</param>
        /// <returns>String with a URI that can be used to login to a specified
        /// location</returns>
        public static string StartLocation(string sim, int x, int y, int z)
        {
            // uri:sim name&x&y&z
            return "uri:" + sim.ToLower() + "&" + x + "&" + y + "&" + z;
        }



        /// <summary>
        /// gives us a working default set MINUS user specific login/pass info.
        /// KISS
        /// </summary>
        public Dictionary<string, object> DefaultLoginValues()
        {
            Dictionary<string, object> values = new Dictionary<string, object>();

            values["start"] = "last";
            values["major"] = 1;
            values["minor"] = 15;
            values["patch"] = 0;
            values["build"] = 2;
            values["channel"] = "slcl";
            values["platform"] = "Win";
            values["mac"] = "00:00:00:00:00:00";
            values["agree_to_tos"] = "true";
            values["read_critical"] = "true";
            values["viewer_digest"] = "0";
            values["user-agent"] = Client.Settings.LIBNAME + " (" + Client.Settings.VERSION + ")";
            values["author"] = Client.Settings.AUTHOR;

            // Build the options array
            List<object> optionsArray = new List<object>();
            optionsArray.Add("inventory-root");
            optionsArray.Add("inventory-skeleton");
            optionsArray.Add("inventory-lib-root");
            optionsArray.Add("inventory-lib-owner");
            optionsArray.Add("inventory-skel-lib");
            optionsArray.Add("initial-outfit");
            optionsArray.Add("gestures");
            optionsArray.Add("event_categories");
            optionsArray.Add("event_notifications");
            optionsArray.Add("classified_categories");
            optionsArray.Add("buddy-list");
            optionsArray.Add("ui-config");
            optionsArray.Add("login-flags");
            optionsArray.Add("global-textures");

            values["options"] = optionsArray;

            return values;
        }

        public void Log(string msg, LogLevel lvl)
        {
            Client.Log("Grid: " + msg, lvl);
        }

        #endregion

        #region Login and Logout

        /// <summary>
        /// takes a Login command message and logs in the client.
        /// Async method.
        /// </summary>
        /// <param name="Msg">Message containing start info.</param>
        /// <returns></returns>
        public void Login(CommandMessage Msg)
        {
            if (Msg.Data != null)
            {
                if( Msg.Data is SimLocation)
                    Client.StartLocation = (SimLocation)Msg.Data;
                else if (Msg.Data is object[] && ((object[])Msg.Data).Length == 4)
                {
                    object[] args = (object[])Msg.Data;
                    if (args[0] is string && args[1] is int && args[2] is int && args[3] is int)
                        Client.StartLocation = new SimLocation((string)args[0], (int)args[1], (int)args[2], (int)args[3]);
                }

            }
            string Error = "";
            bool ret = Login(out Error);
            MQueue.Ack(Msg, (ret ? AckMessage.StatusType.Completed : AckMessage.StatusType.Failed), (ret ? Error : null));
        }


        /// <summary>
        /// Login to the grid via direct blocking call.
        /// </summary>
        public bool Login(out string Error)
        {
            LoginValues["first"] = Client.FirstName;
            LoginValues["last"] = Client.LastName;
            LoginValues["passwd"] = Helpers.MD5(Client.Password);
            LoginValues["start"] = Client.StartLocation.ToUri();
            return DoXMLRpcLogin(LoginValues, "login_to_simulator", LoginURI, out Error);
        }

        /// <summary>
        /// This is the actual RPC login call out on the Grid.
        /// This COULD be punted back to us with a request to login elsewhere, so we have to be able
        /// to call back into ourselves. e.g. reentrant!
        /// </summary>
        /// <param name="method"></param>
        /// <param name="uri"></param>
        /// <returns></returns>
        bool DoXMLRpcLogin(Dictionary<string, object> loginParams, string method, string url, out string Error)
        {
            // our local variables for this login session.
            XmlRpcResponse result;
            XmlRpcRequest xmlrpc;
            Hashtable loginValuesHash;

            // login options are now the domain of the Client to put togeather since as the 
            // login XML, we really don't care a whole lot unless redirected.

            // Re-read the timeout value for the DisconnectTimer

            #region Rebuild To Hash

            // Rebuild the Dictionary<> in to a Hashtable for compatibility with XmlRpcCS
            loginValuesHash = new Hashtable(loginParams.Count);
            foreach (KeyValuePair<string, object> kvp in loginParams)
            {
                if (kvp.Value is IList)
                {
                    IList list = ((IList)kvp.Value);
                    ArrayList array = new ArrayList(list.Count);
                    foreach (object obj in list)
                    {
                        array.Add(obj);
                    }
                    loginValuesHash[kvp.Key] = array;
                }
                else
                {
                    loginValuesHash[kvp.Key] = kvp.Value;
                }
            }

            #endregion

            // Build the XML-RPC request
            xmlrpc = new XmlRpcRequest();
            xmlrpc.MethodName = method;
            xmlrpc.Params.Clear();
          //  Log("Post Clear\r\n" + xmlrpc.ToString(), LogLevel.Debug);
            xmlrpc.Params.Add(loginValuesHash);

            try
            {
            //    Log("Sending XML document to " + url , LogLevel.Debug);
           //     Log(xmlrpc.ToString(), LogLevel.Debug);
                result = (XmlRpcResponse)xmlrpc.Send(url, Client.Settings.LOGIN_TIMEOUT);
            }
            catch (Exception e)
            {
                // FIXME
                // Having a string field for login errrors isn't very clean.
                // We'll have to handle this some other way eventually. 
                // Maybe passing around a CommandMessage?
                Log("Login error: " + e.Message, LogLevel.Error);
                Error = "XML-RPC Error: " + e.Message;
                return false;
            }

            if (result.IsFault)
            {
                Log("Fault " + result.FaultCode + ": " + result.FaultString, LogLevel.Error);
                Error = "XML-RPC Fault: " + result.FaultCode + ": " + result.FaultString;
                return false;
            }
            Log("Parsing XML Response" , LogLevel.Debug);
            Hashtable values = (Hashtable)result.Value;
            LoginResponseValues = new Dictionary<string, object>();
            foreach (DictionaryEntry entry in values)
            {
                LoginResponseValues[(string)entry.Key] = entry.Value;
            }

            if ((string)LoginResponseValues["login"] == "indeterminate")
            {
                string nexturl = (string)LoginResponseValues["next_url"];
                string nextmethod = (string)LoginResponseValues["next_method"];
                string message = (string)LoginResponseValues["message"];
                Log("Login redirected: " + nexturl + ", message: " + message, LogLevel.Info);

                return DoXMLRpcLogin(loginParams, nexturl, nextmethod, out Error);
            }
            else if ((string)LoginResponseValues["login"] == "false")
            {
                Error = LoginResponseValues["reason"] + ": " + LoginResponseValues["message"];
                Log("Login Error: " + Error, LogLevel.Error);
                return false;
            }
            else if ((string)LoginResponseValues["login"] != "true")
            {
                Error = "Unknown error";
                Log("Login Error: " + Error, LogLevel.Error);
                return false;
            }

            System.Text.RegularExpressions.Regex LLSDtoJSON =
                new System.Text.RegularExpressions.Regex(@"('|r([0-9])|r(\-))");
            string json;
            Dictionary<string, object> jsonObject = null;
            LLVector3 vector = LLVector3.Zero;
            LLVector3 posVector = LLVector3.Zero;
            LLVector3 lookatVector = LLVector3.Zero;
            ulong regionHandle = 0;

            try
            {
                if (LoginResponseValues.ContainsKey("look_at"))
                {
                    // Replace LLSD variables with object representations

                    // Convert LLSD string to JSON
                    json = "{vector:" + LLSDtoJSON.Replace((string)LoginResponseValues["look_at"], "$2") + "}";

                    // Convert JSON string to a JSON object
                    jsonObject = JsonFacade.fromJSON(json);
                    JSONArray jsonVector = (JSONArray)jsonObject["vector"];

                    // Convert the JSON object to an LLVector3
                    vector = new LLVector3(Convert.ToSingle(jsonVector[0], CultureInfo.InvariantCulture),
                        Convert.ToSingle(jsonVector[1], CultureInfo.InvariantCulture), Convert.ToSingle(jsonVector[2], CultureInfo.InvariantCulture));

                    LoginResponseValues["look_at"] = vector;
                }
            }
            catch (Exception e)
            {
                Log(e.ToString(), LogLevel.Warning);
                LoginResponseValues["look_at"] = null;
            }

            try
            {
                if (LoginResponseValues.ContainsKey("home"))
                {
                    Dictionary<string, object> home;

                    // Convert LLSD string to JSON
                    json = LLSDtoJSON.Replace((string)LoginResponseValues["home"], "$2");

                    // Convert JSON string to an object
                    jsonObject = JsonFacade.fromJSON(json);

                    // Create the position vector
                    JSONArray array = (JSONArray)jsonObject["position"];
                    posVector = new LLVector3(Convert.ToSingle(array[0], CultureInfo.InvariantCulture), Convert.ToSingle(array[1], CultureInfo.InvariantCulture),
                        Convert.ToSingle(array[2], CultureInfo.InvariantCulture));

                    // Create the look_at vector
                    array = (JSONArray)jsonObject["look_at"];
                    lookatVector = new LLVector3(Convert.ToSingle(array[0], CultureInfo.InvariantCulture),
                        Convert.ToSingle(array[1], CultureInfo.InvariantCulture), Convert.ToSingle(array[2], CultureInfo.InvariantCulture));

                    // Create the regionhandle
                    array = (JSONArray)jsonObject["region_handle"];
                    regionHandle = Helpers.UIntsToLong((uint)(int)array[0], (uint)(int)array[1]);

                    Client.Movement.Position = posVector;
                    Client.Movement.LookAt = lookatVector;

                    // Create a dictionary to hold the home values
                    home = new Dictionary<string, object>();
                    home["position"] = posVector;
                    home["look_at"] = lookatVector;
                    home["region_handle"] = regionHandle;
                    LoginResponseValues["home"] = home;
                }
            }
            catch (Exception e)
            {
                Log(e.ToString(), LogLevel.Warning);
                LoginResponseValues["home"] = null;
            }

            //Decode curent values of the client.
            try
            {
                this.AgentID = new LLUUID((string)LoginResponseValues["agent_id"]);
                this.SessionID = new LLUUID((string)LoginResponseValues["session_id"]);
                this.SecureSessionID = new LLUUID((string)LoginResponseValues["secure_session_id"]);
                Client.ID = this.AgentID;
                // Names are wrapped in quotes now, have to strip those
                Client.FirstName = ((string)LoginResponseValues["first_name"]).Trim(new char[] { '"' });
                Client.LastName = ((string)LoginResponseValues["last_name"]).Trim(new char[] { '"' });
                Client.Movement.LookAt = vector;
                Client.HomePosition = posVector;
                Client.HomeLookAt = lookatVector;

                // Get Inventory Root Folder
                ArrayList alInventoryRoot = (ArrayList)LoginResponseValues["inventory-root"];
                Hashtable htInventoryRoot = (Hashtable)alInventoryRoot[0];

                //FIXME Bump to inventory manager?
                if(null != Client.Inventory)
                    Client.Inventory.InventoryRootFolderUUID = new LLUUID((string)htInventoryRoot["folder_id"]);

                // Set the Circuit Code
                CircuitCode = (uint)(int)LoginResponseValues["circuit_code"];

                if (null == (Sim = ConnectSim((string)LoginResponseValues["sim_ip"]
                                             , (ushort)(int)LoginResponseValues["sim_port"])))
                {
                    Error = "Unable to connect to the initial simulator";
                    Log(Error, LogLevel.Error);
                    return false;
                }

                // Officially moves the client into this initial Sim.
                CompleteAgentMovement(Sim);
                if (!Sim.SimLink.ConnectedEvent.WaitOne(5000, false))
                {
                    Log("Timeout waiting for SimLink Region Handshake", LogLevel.Warning);
                }

                // ask caps to start up on this sim's seed... heh. seed. 
                MQueue.Send(new CommandMessage(CAPSManager.Command.Start
                                                , LoginResponseValues["seed_capability"] ), CapsManager);

                Log("Login complete.", LogLevel.Info);
                MQueue.MatchRegistered(Event.Login, AckMessage.StatusType.Completed, Client.StartLocation);

                Error = "";
                return true;
            }
            catch (Exception e)
            {
                Log("Login error: " + e.ToString(), LogLevel.Error);
            }
            Error = "UNKNOWN FALLTHROUGH. This should NEVER be reached!";
            return false;
         }

        public void Logout(CommandMessage Msg)
        {
            // send logout request packet.
            // waitfor answer
            // if timeout force disconnections.
            // notify registrants.
            RequestLogout();

            MQueue.Ack(Msg, AckMessage.StatusType.Completed);
        }

        private void KickUserHandler(Message msg)
        {
            string message = Helpers.FieldToUTF8String(((KickUserPacket)msg.Data).UserInfo.Reason);
            Log("KICKED by " + Sim.ToString() + " : " + message, LogLevel.Warning);
            // Shutdown the Grid layer
            Shutdown();
        }

        public void Logout()
        {
            Log("Logging out of grid " + Name, LogLevel.Info);
            LogoutReplyEvent.Reset();
            RequestLogout();
            LogoutReplyEvent.WaitOne(Client.Settings.LOGOUT_TIMEOUT, false);
            // clear the values since we are logging out and no longer need them
        }

        /// <summary>
        /// Initiate the logout process (three step process!)
        /// </summary>
        public void RequestLogout()
        {
            // This will catch a Logout when the client is not logged in
            // we always have a Sim. :)
            if (Sim == null)
            {
                Log("No current Sim. returning.", LogLevel.Warning);
                LogoutReplyEvent.Set();
                return;
            }

            Log("Requesting Logout from Sim " + Sim.ToString(), LogLevel.Info);

            // Send a logout request to the current sim
            LogoutRequestPacket logout = new LogoutRequestPacket();
            logout.AgentData.AgentID = AgentID;
            logout.AgentData.SessionID = SessionID;

            // logout jumps the message queue since we're terminating the link by request.
            Sim.SimLink.SendPacket(logout, true);

            LogoutTimer = new System.Timers.Timer(Client.Settings.LOGOUT_TIMEOUT);
            LogoutTimer.AutoReset = false;
            LogoutTimer.Elapsed += new System.Timers.ElapsedEventHandler(LogoutTimer_Elapsed);
            LogoutTimer.Start();
        }

        /// <summary>
        /// Triggered if a LogoutReply is not received
        /// I'd rather use a waitone call..... but that's for later rework if necessary.
        /// </summary>
        private void LogoutTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs ev)
        {
            LogoutTimer.Stop();
            Log("Logout due to timeout on server acknowledgement", LogLevel.Debug);
            ForceLogout();
        }

        /// <summary>
        /// Uses a LogoutDemand packet to force initiate a logout
        /// </summary>
        public void ForceLogout()
        {
            Log("Forcing logout", LogLevel.Info);

            // Insist on shutdown
            LogoutDemandPacket logoutDemand = new LogoutDemandPacket();
            logoutDemand.LogoutBlock.SessionID = SessionID;
            Sim.SimLink.SendPacket(logoutDemand, true);

            FinalizeLogout();
        }

        /// <summary>
        /// Finalize the logout procedure. Close down sockets, etc.
        /// </summary>
        private void FinalizeLogout()
        {
            Log("Finalizing Logout.", LogLevel.Info);
            LogoutTimer.Stop();

            // Shutdown the Grid layer
            Shutdown();

            // notify everyone that we just logged out.
            MQueue.MatchRegistered(new object[] { Event.Logout }, SimpleMessageQueue.AckMessage.StatusType.Completed, null);
            // In case we are blocking in Logout()
            LogoutReplyEvent.Set();
        }

        void Teleport(CommandMessage Msg)
        {
            object[] args = (object[])Msg.Data;
            if (!(args.Length >= 1 && args[0] is ulong && args[1] is LLVector3))
            {
                MQueue.Ack(Msg, AckMessage.StatusType.Failed, "Improper arguments. Must send ulong and vector at the minimal as data!");
                return;
            }
            LLVector3 lookat = LLVector3.Zero;
            LLVector3 location = new LLVector3(0.0f, 1.0f, 0.0f);
            if (args.Length >= 2)
            {
                location = (LLVector3)args[1];
                if (args.Length == 3)
                    lookat = (LLVector3)args[2];
            }
            RequestTeleport((ulong)args[0], location, lookat);
            MQueue.Ack(Msg, AckMessage.StatusType.Started);
        }
        
        /// <summary>
        /// Start a teleport process
        /// </summary>
        /// <param name="regionHandle"></param>
        /// <param name="position">Position for Teleport</param>
        public void RequestTeleport(ulong regionHandle, LLVector3 position)
        {
            RequestTeleport(regionHandle, position, new LLVector3(0.0f, 1.0f, 0.0f));
        }

        /// <summary>
        /// Start a teleport process
        /// </summary>
        /// <param name="regionHandle"></param>
        /// <param name="position">Position for Teleport</param>
        /// <param name="lookAt">Target to look at</param>
        public void RequestTeleport(ulong regionHandle, LLVector3 position, LLVector3 lookAt)
        {
            TeleportLocationRequestPacket teleport = new TeleportLocationRequestPacket();
            teleport.AgentData.AgentID = AgentID;
            teleport.AgentData.SessionID = SessionID;
            teleport.Info.LookAt = lookAt;
            teleport.Info.Position = position;
            teleport.Info.RegionHandle = regionHandle;
            Log("Teleporting to region " + regionHandle.ToString(), LogLevel.Info);
            SendPacket(teleport);
        }

        /// <summary>
        /// Respond to a teleport lure by either accepting it and initiating 
        /// the teleport, or denying it
        /// </summary>
        /// <param name="requesterID">UUID of the avatar requesting the teleport</param>
        /// <param name="accept">Accept the teleport request or deny it</param>
        public void TeleportLureRespond(LLUUID requesterID, bool accept)
        {
            Client.Communication.InstantMessage(Client.FirstName + " " + Client.LastName, requesterID, String.Empty, LLUUID.Random(),
                accept ? CommunicationManager.InstantMessageDialog.AcceptTeleport : CommunicationManager.InstantMessageDialog.DenyTeleport,
                CommunicationManager.InstantMessageOnline.Offline, Client.Movement.Position, LLUUID.Zero, new byte[0]);

            if (accept)
            {
                TeleportLureRequestPacket lure = new TeleportLureRequestPacket();

                lure.Info.AgentID = AgentID;
                lure.Info.SessionID = SessionID;
                lure.Info.LureID = AgentID;
                lure.Info.TeleportFlags = (uint)TeleportFlags.ViaLure;

                SendPacket(lure);
            }
        }

        void LookupSim(CommandMessage Msg)
        {
            string name = (string)Msg.Data;
            SimLookupRequests.Add(name, Msg);
            BeginGetGridRegion(name);
            MQueue.Ack(Msg, AckMessage.StatusType.Started);
        }

       /// <summary>
        /// Begin process to get information for a Region
        /// </summary>
        /// <param name="name">Region name you're requesting data for</param>
        public void BeginGetGridRegion(string name)
        {
            MapNameRequestPacket map = new MapNameRequestPacket();

            map.AgentData.AgentID = AgentID;
            map.AgentData.SessionID = SessionID;
            map.NameData.Name = Helpers.StringToField(name.ToLower());

            SendPacket(map);
        }

                #endregion

        #region Simulator Management

        /// <summary>
        /// Connects to the simulator specified by ip:port and starts a caps queue on it.
        /// </summary>
        /// <param name="IP"></param>
        /// <param name="Port"></param>
        /// <param name="CAPS"></param>
        /// <returns></returns>
        public Simulator ConnectSim(IPAddress IP, ushort Port)
        {
            return ConnectSim(new IPEndPoint(IP, Port));
        }
        public Simulator ConnectSim(string IP, ushort Port)
        {
            return ConnectSim(IPAddress.Parse(IP), Port);
        }

        public Simulator ConnectSim(IPEndPoint ep)
        {
            try
            {
                Simulator newSim = FindSimByEndpoint(ep);
                if(newSim == null)
                    newSim = new Simulator(this, ep);
                if (!newSim.IsConnected())
                {
                    Log("Connecting to sim at " + ep.ToString(), LogLevel.Info);
                    if (!newSim.Connect())
                    {
                        Log("Unable to connect to the simulator at " + ep.ToString(), LogLevel.Error);
                        return null;
                    }
                }
                return newSim;
            }
            catch (Exception e)
            {
                Log(e.ToString(), LogLevel.Critical);
                return null;
            }
        }

        /// <summary>
        /// Move an agent in to a simulator. This packet is the last packet
        /// needed to complete the transition in to a new simulator.
        /// Once this is sent, the agent is officially present "physically" in that sim.
        /// </summary>
        /// <param name="simulator"></param>
        public void CompleteAgentMovement(Simulator destinationSim)
        {
            CompleteAgentMovementPacket move = new CompleteAgentMovementPacket();

            move.AgentData.AgentID = AgentID;
            move.AgentData.SessionID = SessionID;
            move.AgentData.CircuitCode = CircuitCode;

 
            Simulator old = Sim;

            Sim = destinationSim;
            // we need to use the simlink directly due to the special nature of this packet.
            Log("Moving client into sim " + destinationSim.ToString(), LogLevel.Info);
            Sim.SimLink.SendPacket(move);
 
            MQueue.MatchRegistered(new object[] { Event.CurrentSimChanged }, AckMessage.StatusType.Completed, new object[] { old, Sim });
        }

        /// <summary>
        /// locates a simulator by it's IP:port combo
        /// </summary>
        /// <param name="IP"></param>
        /// <param name="Port"></param>
        /// <returns></returns>
        public Simulator FindSimByEndpoint(string IP, string Port)
        {
            IPEndPoint ep = new IPEndPoint(IPAddress.Parse(IP), System.Convert.ToInt16(Port));
            return FindSimByEndpoint(ep);
        }

        public Simulator FindSimByEndpoint(IPEndPoint ep)
        {
            if (SimByConnection.ContainsKey(ep))
                return SimByConnection[ep];
            return null;
        }

        /// <summary>
        /// callback from simlink so we can organize sims.
        /// </summary>
        /// <param name="sim"></param>
        public void SimConnected(Simulator sim)
        {
            // update oru list of connected sims and fire connections.
            // we should only get here once the sim has connected and received it's informational packet.
            Connected = true;
            if (sim.SimLink != null && ! SimByConnection.ContainsKey(sim.SimLink.ipEndPoint))
                SimByConnection.Add(sim.SimLink.ipEndPoint, sim);
            else
                Log("Not adding sim " + sim.ToString() + " to SimByCOnnection", LogLevel.Info);
            if (sim.Name.Length > 0 && !SimulatorsByName.ContainsKey(sim.Name))
                SimulatorsByName.Add(sim.Name, sim);
            else
                Log("Not adding sim " + sim.ToString() + " to SimulatorsByName", LogLevel.Info);
            MQueue.MatchRegistered(new object[] { Event.SimulatorConnected }, AckMessage.StatusType.Completed, new object[] { sim });
        }


        /// <summary>
        /// callback from simlink so we can organize on disconnect.
        /// </summary>
        /// <param name="sim"></param>
        public void SimDisconnected(Simulator sim)
        {
            // manage connection list, if none left, or we didn't get connected PRIOR to this being
            // the current sim being disconnected then we're disconnected. 
            // we should now toast the remaining sim links.
            // Notify registrants
            if (SimByConnection.ContainsKey(sim.SimLink.ipEndPoint))
                SimByConnection.Remove(sim.SimLink.ipEndPoint);
            if (SimulatorsByName.ContainsKey(sim.Name))
                SimulatorsByName.Remove(sim.Name);

            //simdisconnected call.
            if (SimByConnection.Count == 0)
            {
                Connected = false;
                // on disconnect call.
            }
            MQueue.MatchRegistered(new object[] { Event.SimulatorDisconnected }, AckMessage.StatusType.Completed, new object[] { sim });
        }

        /// <summary>
        /// Shutdown will disconnect all the sims except for the current sim
        /// first, and then kill the connection to CurrentSim.
        /// </summary>
        private void Shutdown()
        {
            Shutdown(DisconnectType.ClientInitiated);
        }

        private void Shutdown(DisconnectType type)
        {
            Log("GridManager shutdown initiated", LogLevel.Info);

            foreach (Simulator sim in SimByConnection.Values)
            {
                Log("Disconnecting " + sim.ToString(), LogLevel.Info);
                if (sim != Sim) MQueue.Send(new CommandMessage(SimulatorConnection.Commands.Shutdown, null), sim.SimLink);
            }

            // sleep until all sims have dropped their conenctions.            
            while (SimByConnection.Count > 1)
            {
                System.Threading.Thread.Sleep(10);
            }

            if (Sim != null)
            {
                Log("Disconnecting final sim " + Sim.ToString(), LogLevel.Info);
                // Kill the connection to the curent simulator directly...
                Sim.Disconnect();

                // Fire the SimDisconnected event if a handler is registered
                // Destroy the CurrentSim object
                Sim = null;
            }

            // Stop the curent caps system. We'll reinit it later on login, if we login again. :)
            MQueue.Send(new CommandMessage(CAPSManager.Command.Stop), CapsManager);
            Log("Shutdown complete.", LogLevel.Info);
            Connected = false;
            MQueue.MatchRegistered(Event.Logout, AckMessage.StatusType.Completed, type);
        }

        /// <summary>
        /// Packet forwarding to the current Simulator for sending.
        /// </summary>
        /// <param name="p"></param>
        public void SendPacket(Packet p)
        {
            if (null == Sim || !Sim.IsConnected())
                return;
            MQueue.Send(new CommandMessage(SimulatorConnection.Commands.Send, new object[] { p }), Sim.SimLink);
        }


        void FetchAllSims(MapLayerType layer)
        {
            MapBlockRequestPacket request = new MapBlockRequestPacket();

            request.AgentData.AgentID = AgentID;
            request.AgentData.SessionID = SessionID;
            request.AgentData.EstateID = 0; // TODO: ?
            request.AgentData.Flags = (uint)layer;
            request.PositionData.MaxX = 65535;
            request.PositionData.MaxY = 65535;
            request.PositionData.MinX = 0;
            request.PositionData.MinY = 0;

            SendPacket(request);
        }

#endregion


        #region Handlers
 
        private void EnableSimulatorHandler(Message msg)
        {
            if (!Client.Settings.CROSS_BORDERS) return;
            PacketCarrier carrier = (PacketCarrier)msg.Data;
            Log("Received EnableSimulator packet.", LogLevel.Debug);
            EnableSimulatorPacket p = (EnableSimulatorPacket)carrier.packet;
            IPEndPoint endPoint = new IPEndPoint(p.SimulatorInfo.IP, p.SimulatorInfo.Port);

            // FIXME: actually connect to the sim we were asked to conenct to.
        }

        /// <summary>
        /// wrapper. we'll deal with teleportcomplete directly from caps eventually. *sigh* no time.
        /// </summary>
        /// <param name="Ack"></param>
        void TeleportHandler(AckMessage Ack)
        {
            PacketCarrier carrier = (PacketCarrier)Ack.Data;
            TeleportHandler(carrier.packet, carrier.sim);
        }

        /// <param name="packet">Incoming TeleportHandler packet</param>
        /// <param name="simulator">Simulator sending teleport information</param>
        private void TeleportHandler(Packet packet, Simulator simulator)
        {
            bool finished = false;
            TeleportFlags flags = TeleportFlags.Default;

            if (packet.Type == PacketType.TeleportStart)
            {
                TeleportStartPacket start = (TeleportStartPacket)packet;

                teleportMessage = "Teleport started";
                flags = (TeleportFlags)start.Info.TeleportFlags;
                TeleportStat = TeleportStatus.Start;

                Log("TeleportStart received from " + simulator.ToString() + ", Flags: " + flags.ToString(), LogLevel.Debug);
            }
            else if (packet.Type == PacketType.TeleportProgress)
            {
                TeleportProgressPacket progress = (TeleportProgressPacket)packet;

                teleportMessage = Helpers.FieldToUTF8String(progress.Info.Message);
                flags = (TeleportFlags)progress.Info.TeleportFlags;
                TeleportStat = TeleportStatus.Progress;

                Log("TeleportProgress received from " + simulator.ToString() + "Message: " +
                    teleportMessage + ", Flags: " + flags.ToString(), LogLevel.Debug);
            }
            else if (packet.Type == PacketType.TeleportFailed)
            {
                TeleportFailedPacket failed = (TeleportFailedPacket)packet;

                teleportMessage = Helpers.FieldToUTF8String(failed.Info.Reason);
                TeleportStat = TeleportStatus.Failed;
                finished = true;

                Log("TeleportFailed received from " + simulator.ToString() + ", Reason: " + teleportMessage, LogLevel.Debug);
            }
            else if (packet.Type == PacketType.TeleportFinish)
            {
                TeleportFinishPacket finish = (TeleportFinishPacket)packet;

                flags = (TeleportFlags)finish.Info.TeleportFlags;
                string seedcaps = Helpers.FieldToUTF8String(finish.Info.SeedCapability);
                finished = true;

                Client.Log("TeleportFinish received from " + simulator.ToString() + ", Flags: " + flags.ToString(), LogLevel.Debug);

                // Connect to the new sim
                Simulator newSimulator = ConnectSim(new IPAddress(finish.Info.SimIP),
                    finish.Info.SimPort);

                if (newSimulator != null)
                {
                    MQueue.Send(new CommandMessage(CAPSManager.Command.ReSeed, seedcaps), CapsManager);
                    CompleteAgentMovement(newSimulator);
                    teleportMessage = "Teleport finished";
                    TeleportStat = TeleportStatus.Finished;
                    // Disconnect from the previous sim
                    simulator.Disconnect();
                    Log("Moved to new sim " + newSimulator.ToString(), LogLevel.Info);
                }
                else
                {
                    teleportMessage = "Failed to connect to the new sim after a teleport";
                    TeleportStat = TeleportStatus.Failed;

                    // Attempt to reconnect to the previous simulator
                    // TODO: This hasn't been tested at all
                    simulator.Connect();
                    MQueue.Send(new CommandMessage(CAPSManager.Command.ReSeed, seedcaps), CapsManager);
                    Log(teleportMessage, LogLevel.Warning);
                }
            }
            else if (packet.Type == PacketType.TeleportCancel)
            {
                //TeleportCancelPacket cancel = (TeleportCancelPacket)packet;

                teleportMessage = "Cancelled";
                TeleportStat = TeleportStatus.Cancelled;
                finished = true;

                Client.Log("TeleportCancel received from " + simulator.ToString(), LogLevel.Debug);
            }
            else if (packet.Type == PacketType.TeleportLocal)
            {
                TeleportLocalPacket local = (TeleportLocalPacket)packet;

                teleportMessage = "Teleport finished";
                flags = (TeleportFlags)local.Info.TeleportFlags;
                TeleportStat = TeleportStatus.Finished;
                Client.Movement.LookAt = local.Info.LookAt;
                Client.Movement.Position = local.Info.Position;
                // This field is apparently not used for anything
                //local.Info.LocationID;
                finished = true;

                Client.Log("TeleportLocal received from " + simulator.ToString() + ", Flags: " + flags.ToString(), LogLevel.Debug);
            }

            //if (OnTeleport != null)
            //{
             //   try { OnTeleport(teleportMessage, TeleportStat, flags); }
              //  catch (Exception e) { Client.Log(e.ToString(), LogLevel.Error); }
           // }

            if (finished) TeleportEvent.Set();
        }

        /// <summary>
        /// parses the XML sent back from CAPS for teleportation events.
        /// </summary>
        /// <param name="message"></param>
        /// <param name="body"></param>
        private void TeleportFinishedHandler(AckMessage Ack)
        {
            CAPSManager.CAPSEventCarrier carrier = (CAPSManager.CAPSEventCarrier)Ack.Data;
            string message = carrier.Event;
            Hashtable body = carrier.Body;

            Hashtable info = (Hashtable)body["Info"];

            // Backwards compatibility hack
            TeleportFinishPacket packet = new TeleportFinishPacket();

            packet.Info.SimIP = Helpers.BytesToUIntBig((byte[])info["SimIP"]);
            packet.Info.LocationID = Helpers.BytesToUInt((byte[])info["LocationID"]);
            packet.Info.TeleportFlags = Helpers.BytesToUInt((byte[])info["TeleportFlags"]);
            packet.Info.AgentID = (LLUUID)info["AgentID"];
            packet.Info.RegionHandle = Helpers.BytesToUInt64((byte[])info["RegionHandle"]);
            packet.Info.SeedCapability = Helpers.StringToField((string)info["SeedCapability"]);
            packet.Info.SimPort = (ushort)(int)info["SimPort"];
            packet.Info.SimAccess = (byte)(int)info["SimAccess"];

            Client.Log(String.Format(
                "Received a TeleportFinish event from {0}, SimIP: {1}, Location: {2}, RegionHandle: {3}",
                Sim.ToString(), packet.Info.SimIP, packet.Info.LocationID, packet.Info.RegionHandle), LogLevel.Debug);

            TeleportHandler(packet, Sim);
        }

        private void LogoutReplyHandler(Packet packet, Simulator simulator)
        {
            LogoutReplyPacket logout = (LogoutReplyPacket)packet;

            if ((logout.AgentData.SessionID == SessionID) && (logout.AgentData.AgentID == AgentID))
            {
                Client.Log("Logout negotiated with server", LogLevel.Debug);

                // Deal with callbacks, if any
                List<LLUUID> itemIDs = new List<LLUUID>();

                foreach (LogoutReplyPacket.InventoryDataBlock InventoryData in logout.InventoryData)
                {
                    itemIDs.Add(InventoryData.ItemID);
                }
                MQueue.MatchRegistered(Event.Logout, AckMessage.StatusType.Completed, itemIDs);

                FinalizeLogout();
            }
            else
            {
                Log("Invalid Session or Agent ID received in Logout Reply... ignoring", LogLevel.Warning);
            }
        }

        private void EnableSimulatorHandler(Packet packet, Simulator simulator)
        {
            if (!Client.Settings.MULTIPLE_SIMS) return;

            EnableSimulatorPacket p = (EnableSimulatorPacket)packet;
            IPEndPoint endPoint = new IPEndPoint(p.SimulatorInfo.IP, p.SimulatorInfo.Port);

            // First, check to see if we've already started connecting to this sim
            if (FindSimByEndpoint(endPoint) != null) return;

            IPAddress address = new IPAddress(p.SimulatorInfo.IP);
            if (ConnectSim(address, p.SimulatorInfo.Port) == null)
            {
                Log("Unabled to connect to new sim " + address + ":" + p.SimulatorInfo.Port,
                    LogLevel.Error);
            }
        }

        private void KickUserHandler(Packet packet, Simulator simulator)
        {
            string message = Helpers.FieldToUTF8String(((KickUserPacket)packet).UserInfo.Reason);

            // Shutdown the Grid layer
            Shutdown(DisconnectType.ServerInitiated);

            MQueue.MatchRegistered(Event.Logout, AckMessage.StatusType.Completed);
        }

        /// <summary>
        /// Populate Grid info based on data from MapBlockReplyPacket
        /// </summary>
        /// <param name="packet">Incoming MapBlockReplyPacket packet</param>
        /// <param name="simulator">Unused</param>
        private void MapBlockReplyHandler(AckMessage Ack)
        {
            PacketCarrier carrier = (PacketCarrier)Ack.Data;
            Simulator simulator;
            MapBlockReplyPacket map = (MapBlockReplyPacket)carrier.packet;

            foreach (MapBlockReplyPacket.DataBlock block in map.Data)
            {
                if (block.X != 0 && block.Y != 0)
                {
                    simulator = new Simulator(this);

                    simulator.X = block.X;
                    simulator.Y = block.Y;
                    simulator.Name = Helpers.FieldToUTF8String(block.Name);
                    simulator.RegionFlags = block.RegionFlags;
                    simulator.WaterHeight = block.WaterHeight;
                    simulator.Agents = block.Agents;
                    simulator.Access = block.Access;
                    simulator.MapImageID = block.MapImageID;
                    simulator.RegionHandle = Helpers.UIntsToLong((uint)simulator.X * (uint)256, (uint)simulator.Y * (uint)256);

                    //lock (Regions) Regions[simulator.Name.ToLower()] = simulator;
                    //lock (RegionsByHandle) RegionsByHandle[simulator.RegionHandle] = simulator;
                    if(!SimulatorsByName.ContainsKey(simulator.Name.ToLower()))
                        SimulatorsByName.Add(simulator.Name.ToLower(), simulator);
                    MQueue.MatchRegistered(Event.RegionAdd, AckMessage.StatusType.Completed, simulator);
                    Log("Got details for " + simulator.Name + " at " + simulator.Handle.ToString(), LogLevel.Debug);
                }
            }
        }

        /// <summary>
        /// Called to deal with LogoutReply packet and fires off callback
        /// </summary>
        /// <param name="packet">Full packet of type LogoutReplyPacket</param>
        /// <param name="simulator"></param>
        public void LogoutReplyHandler(AckMessage Ack)
        {
            PacketCarrier carrier = (PacketCarrier)Ack.Data;
            LogoutReplyPacket logout = (LogoutReplyPacket)carrier.packet;

            if ((logout.AgentData.SessionID == SessionID) && (logout.AgentData.AgentID == AgentID))
            {
                Log("Logout negotiated with server", LogLevel.Debug);

                // Deal with callbacks, if any
                // what the HECK is this stuff all about? THe Logout event is in FinalizeLogout()!

                //                List<LLUUID> itemIDs = new List<LLUUID>();
                //              foreach (LogoutReplyPacket.InventoryDataBlock InventoryData in logout.InventoryData)
                //            {
                //              itemIDs.Add(InventoryData.ItemID);
                //        }
                FinalizeLogout();
            }
            else
            {
                Log("Invalid Session or Agent ID received in Logout Reply... ignoring", LogLevel.Warning);
            }
        }


        #endregion
    }
}
